package com.wordpress.standal.multimethod.api.value2

import scala.collection.mutable.Map

class MultiMethod[C1 <: Any, C2 <: Any, C3, R] {
  
  private val all = Map[(C1, C2), Function3[C1, C2, C3, R]]()
  private val dim1 = Map[C1, Function3[C1, C2, C3, R]]()
  private val dim2 = Map[C2, Function3[C1, C2, C3, R]]()
  private var default : Function3[C1, C2, C3, R] = null

  def add(cls1: Class[C1], cls2: Class[C2], func: Function3[C1, C2, C3, R]) {
    default = func
  }

  def add(cls1: Class[C1], val2: C2, func: Function3[C1, C2, C3, R]) {
    dim2 += val2 -> func
  }

  def add(val1: C1, cls2: Class[C2], func: Function3[C1, C2, C3, R]) {
    dim1 += val1 -> func
  }

  def add(val1: C1, val2: C2, func: Function3[C1, C2, C3, R]) {
    all += (val1, val2) -> func
  }

  def apply(val1: C1, val2: C2, val3: C3) : R = {
    var func: Function3[C1, C2, C3, R] = null
    if (all.contains(val1, val2)) {
      func = all(val1, val2)
    }
    if (func == null && dim1.contains(val1)) {
      func = dim1(val1)
    }
    if (func == null && dim2.contains(val2)) {
      func = dim2(val2)
    }
    if (func == null) {
      func = default
    }
    func.apply(val1, val2, val3)
  }
  
}

object MultiMethod {
  
  class MultiMethod0 [C1 <: Any, C2 <: Any, R] {
    val rep = new MultiMethod[C1, C2, Unit, R]
    def apply(c1 : C1, c2: C2) = {
      rep.apply(c1, c2, null)
    }
    def add(cls1: Class[C1], cls2: Class[C2], func: Function2[C1, C2, R]) {
      rep.add(cls1, cls2, func)
    }
    def add(cls1: Class[C1], val2: C2, func: Function2[C1, C2, R]) {
      rep.add(cls1, val2, func)	
    }
    def add(val1: C1, cls2: Class[C2], func: Function2[C1, C2, R]) {
      rep.add(val1, cls2, func)    	
    }
    def add(val1: C1, val2: C2, func: Function2[C1, C2, R]) {
      rep.add(val1, val2, func)    	
    }
    private implicit def convert(f : Function2[C1, C2, R]) : Function3[C1, C2, Unit, R] = {
      new Function3[C1, C2, Unit, R]() {
        def apply(c1: C1, c2: C2, u: Unit) = f.apply(c1, c2)
      }
    }
  }

  class MultiMethod2 [C1 <: Any, C2 <: Any, C3, C4, R] {
    val rep = new MultiMethod[C1, C2, (C3, C4), R]
    def apply(c1 : C1, c2: C2, c3: C3, c4: C4) = {
      rep.apply(c1, c2, (c3, c4))
    }
    def add(cls1: Class[C1], cls2: Class[C2], func: Function4[C1, C2, C3, C4, R]) {
      rep.add(cls1, cls2, func)
    }
    def add(cls1: Class[C1], val2: C2, func: Function4[C1, C2, C3, C4, R]) {
      rep.add(cls1, val2, func)
    }
    def add(val1: C1, cls2: Class[C2], func: Function4[C1, C2, C3, C4, R]) {
      rep.add(val1, cls2, func)
    }
    def add(val1: C1, val2: C2, func: Function4[C1, C2, C3, C4, R]) {
      rep.add(val1, val2, func)
    }
    private implicit def convert(f : Function4[C1, C2, C3, C4, R]) : Function3[C1, C2, (C3, C4), R] = {
      new Function3[C1, C2, (C3, C4), R]() {
        def apply(c1: C1, c2: C2, c3: (C3, C4)) = f.apply(c1, c2, c3._1, c3._2)
      }
    }
  }

  def apply[C1 <: Any, C2 <: Any, R]() = {
    new MultiMethod0[C1, C2, R]()
  }
  
  def apply[C1 <: Any, C2 <: Any, C3, R]() = {
    new MultiMethod[C1, C2, C3, R]()
  }

  def apply[C1 <: Any, C2 <: Any, C3, C4, R]() = {
    new MultiMethod2[C1, C2, C3, C4, R]()
  }

}
